﻿#include "widgetgraphicsview.h"
#include "Public/appsignal.h"
#include "Graphics/picturerectitem.h"

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/types_c.h"
#include "opencv2/imgproc.hpp"
#include "opencv2/opencv.hpp"

#include <QGridLayout>
#include <QScrollBar>
#include <QWheelEvent>
#include <QtMath>
#include <QImageWriter>
#include <thread>
#include <vector>
#include <QPushButton>
#include <QSpacerItem>
#include <QVBoxLayout>
#include <QDateTime>
#include <QStandardPaths>
#include <QLabel>

using namespace cv;
using namespace std;

// test
#include <QDebug>
#include <QGraphicsRectItem>
#include <QDateTime>

WidgetGraphicsView::WidgetGraphicsView(QWidget *parent)
    : QWidget{parent}
{
    init();
}

void WidgetGraphicsView::init()
{
    int width = this->width();
    int height = this->height();
    // 视图模型
    mGraphicsScene.setSceneRect(width * 0.5 * -1, height * 0.5 * -1, width, height);
    mGraphicsScene.setBackgroundBrush(QBrush(QColor(96, 102, 129)));

    mGraphicsView.setScene(&mGraphicsScene);
    mGraphicsView.setOptimizationFlags(QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing);
    mGraphicsView.setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
    mGraphicsView.setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    mGraphicsView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    mGraphicsView.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    mGraphicsView.setRenderHint(QPainter::SmoothPixmapTransform);
    mGraphicsView.setDragMode(QGraphicsView::RubberBandDrag);
    mGraphicsView.setInteractive(true);
    mGraphicsView.setFocusPolicy(Qt::NoFocus);
    mGraphicsView.centerOn(0, 0);

    QFont font("Microsoft YaHei", 9);
    QFontMetrics metrics(font);
    int margin = metrics.height() * 0.5;

    QWidget *widgetBase = new QWidget(this);
    widgetBase->setObjectName("widgetGraphicsViewBase");
    widgetBase->setFocusPolicy(Qt::NoFocus);

    QGridLayout *layout = new QGridLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(widgetBase);
    setLayout(layout);

    QGridLayout *layoutWidgetBase = new QGridLayout(widgetBase);
    layoutWidgetBase->addWidget(&mGraphicsView);
    layoutWidgetBase->setContentsMargins(margin, margin, margin, margin);
    widgetBase->setLayout(layoutWidgetBase);
    mGraphicsView.setStyleSheet("border: none;");

    // 浮动工具
    QWidget *widgetHoverBase = new QWidget(this);
    widgetHoverBase->setGeometry(20, 20, 0, 0);
    widgetHoverBase->setObjectName("widgetHoverBase");

    QVBoxLayout *layoutHoverBase = new QVBoxLayout(widgetHoverBase);
    layoutHoverBase->setContentsMargins(0, margin, 0, 0);
    layoutHoverBase->setSpacing(margin);

    QPushButton *btnResetView = new QPushButton(widgetHoverBase);
    btnResetView->setObjectName("btnResetView");
    btnResetView->setProperty("SmallHoverButton", true);
    btnResetView->setFocusPolicy(Qt::NoFocus);
    btnResetView->setToolTip("重定位视图到合适位置");

    QPushButton *btnCloseSelect = new QPushButton(widgetHoverBase);
    btnCloseSelect->setObjectName("btnCloseSelect");
    btnCloseSelect->setProperty("SmallHoverButton", true);
    btnCloseSelect->setFocusPolicy(Qt::NoFocus);
    btnCloseSelect->setToolTip("关闭选中图像");

    QPushButton *btnRestoreView = new QPushButton(widgetHoverBase);
    btnRestoreView->setObjectName("btnRestoreView");
    btnRestoreView->setProperty("SmallHoverButton", true);
    btnRestoreView->setFocusPolicy(Qt::NoFocus);
    btnRestoreView->setToolTip("恢复视图原始状态");

    QPushButton *btnSaveView = new QPushButton(widgetHoverBase);
    btnSaveView->setObjectName("btnSaveView");
    btnSaveView->setProperty("SmallHoverButton", true);
    btnSaveView->setFocusPolicy(Qt::NoFocus);
    btnSaveView->setToolTip("保存图像");

    layoutHoverBase->addWidget(btnResetView);
    layoutHoverBase->addWidget(btnCloseSelect);
    layoutHoverBase->addWidget(btnRestoreView);
    layoutHoverBase->addWidget(btnSaveView);
    layoutHoverBase->setAlignment(Qt::AlignHCenter);
    layoutHoverBase->addItem(new QSpacerItem(20, 20, QSizePolicy::Preferred, QSizePolicy::Expanding));
    widgetHoverBase->setLayout(layoutHoverBase);

    connect(btnResetView, &QPushButton::clicked, this, &WidgetGraphicsView::slot_btn_reset_view_click);
    connect(btnCloseSelect, &QPushButton::clicked, this, &WidgetGraphicsView::slot_btn_close_select_click);
    connect(btnRestoreView, &QPushButton::clicked, this, &WidgetGraphicsView::slot_btn_restore_view_click);
    connect(btnSaveView, &QPushButton::clicked, this, &WidgetGraphicsView::slot_btn_save_view_click);

    // 信号
    connect(AppSignal::getInstance(), &AppSignal::sgl_add_new_picture, this, &WidgetGraphicsView::slot_add_new_picture);
    connect(AppSignal::getInstance(), &AppSignal::sgl_wheel_graphics_view, this, &WidgetGraphicsView::slot_wheel_graphics_view);
    connect(AppSignal::getInstance(), &AppSignal::sgl_save_current_visible_scene, this, &WidgetGraphicsView::slot_save_current_visible_scene);
    connect(AppSignal::getInstance(), &AppSignal::sgl_add_new_graphics_item, this, &WidgetGraphicsView::slot_add_new_graphics_item);
    connect(AppSignal::getInstance(), &AppSignal::sgl_del_all_graphics_item, this, &WidgetGraphicsView::slot_del_all_graphics_item);
    connect(AppSignal::getInstance(), &AppSignal::sgl_close_slected_graphics_item, this, &WidgetGraphicsView::slot_close_slected_graphics_item);

    // 定位点
//    QGraphicsRectItem *item = new QGraphicsRectItem;
//    item->setRect(0, 0, 10, 10);
//    item->setBrush(QBrush(QColor(0, 255, 0)));
//    item->setPos(-5, -5);
//    item->setZValue(100);
//    mGraphicsScene.addItem(item);
}

void WidgetGraphicsView::switchInteractiveMode(EnumDragMode mode)
{
    if (mode == EnumDragMode::Mode_Select)
    {
        if (mGraphicsView.dragMode() == QGraphicsView::RubberBandDrag) return;
        mGraphicsView.setDragMode(QGraphicsView::RubberBandDrag);
        mGraphicsView.setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        mGraphicsView.setInteractive(true);

        qDebug() << "WidgetGraphicsView::switchInteractiveMode RubberBandDrag";
    }
    else if (mode == EnumDragMode::Mode_Drag)
    {
        if (mGraphicsView.dragMode() == QGraphicsView::ScrollHandDrag) return;
        mGraphicsView.setDragMode(QGraphicsView::ScrollHandDrag);
        mGraphicsView.setTransformationAnchor(QGraphicsView::AnchorViewCenter);
        mGraphicsView.setInteractive(false);

        qDebug() << "WidgetGraphicsView::switchInteractiveMode ScrollHandDrag";
    }
}

void WidgetGraphicsView::switchSelectedItemVisiblity()
{
    // 一旦被隐藏，就不再是选中状态，这里要再考虑一下
//    auto list = mGraphicsScene.selectedItems();
//    for (auto &item : list)
//    {
//        if (nullptr == item) continue;
//        item->setVisible(!item->isVisible());
//    }
}

void WidgetGraphicsView::slot_add_new_picture(const QString &path, int index)
{
    // 此处需要计算图片位置，是一个耗时操作，放到线程中执行
    auto func = [this, path, index]
    {
        std::lock_guard<std::mutex> lock(mMapMutex);
        if (mMapSourceInfo.empty() || index == 1)
        {
            qDebug() << "empty info";
            SourceItemInfo info {index, path, 0, 0, 0, 0, 0, true};
            PictureRectItem *item = new PictureRectItem(path, index, true);
            info.width = item->width();
            info.height = item->height();
            info.rotate = item->rotate();
            info.centerx = 0;
            info.centery = 0;

            mMapSourceInfo.insert(index, info);

            emit AppSignal::getInstance()->sgl_add_new_graphics_item(index, item);
        }
        else
        {
            // 找前序图片，找不到的话，默认用最后一个
            SourceItemInfo preInfo = mMapSourceInfo.contains(index - 1) ? mMapSourceInfo.value(index - 1) : mMapSourceInfo.last();

            qDebug() << "A " << QDateTime::currentDateTime().toMSecsSinceEpoch();

            Mat imageOrigin1 = imread(preInfo.path.toStdString(), 1);
            Mat imageOrigin2 = imread(path.toStdString(), 1);

            qDebug() << "B " << QDateTime::currentDateTime().toMSecsSinceEpoch();

            // 灰度图转换
            Mat image1, image2;
            cvtColor(imageOrigin1, image1, CV_RGB2GRAY);
            cvtColor(imageOrigin2, image2, CV_RGB2GRAY);

            // 提取特征点
            // nfeatures - 最多提取的特征点的数量；
            // scaleFactor - 金字塔图像之间的尺度参数，类似于SIFT中的；
            // nlevels – 高斯金字塔的层数；
            // edgeThreshold – 边缘阈值，这个值主要是根据后面的patchSize来定的，靠近边缘edgeThreshold以内的像素是不检测特征点的。
            // firstLevel - 看过SIFT都知道，我们可以指定第一层的索引值，这里默认为0。
            // WET_K - 用于产生BIREF描述子的 点对的个数，一般为2个，也可以设置为3个或4个，那么这时候描述子之间的距离计算就不能用汉明距离了，而是应该用一个变种。OpenCV中，如果设置WET_K = 2，则选用点对就只有2个点，匹配的时候距离参数选择NORM_HAMMING，
            // 如果WET_K设置为3或4，则BIREF描述子会选择3个或4个点，那么后面匹配的时候应该选择的距离参数为NORM_HAMMING2。
            // scoreType - 用于对特征点进行排序的算法，你可以选择HARRIS_SCORE，也可以选择FAST_SCORE，但是它也只是比前者快一点点而已。
            // patchSize – 用于计算BIREF描述子的特征点邻域大小。

            Ptr<ORB> siftDtc = ORB::create(3000);
            vector<KeyPoint> keyPoint1, keyPoint2;
            siftDtc->detect(image1, keyPoint1);
            siftDtc->detect(image2, keyPoint2);

            //特征点描述，为下边的特征点匹配做准备
            Ptr<ORB> SurfDescriptor = ORB::create();
            Mat imageDesc1, imageDesc2;
            SurfDescriptor->compute(image1, keyPoint1, imageDesc1);
            SurfDescriptor->compute(image2, keyPoint2, imageDesc2);

            flann::Index flannIndex(imageDesc1, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);
            vector<DMatch> GoodMatchePoints;
            Mat macthIndex(imageDesc2.rows, 2, CV_32SC1), matchDistance(imageDesc2.rows, 2, CV_32FC1);
            flannIndex.knnSearch(imageDesc2, macthIndex, matchDistance, 2, flann::SearchParams());

            int pointCount = matchDistance.rows;
            // Lowe's algorithm,获取优秀匹配点
            for (int i = 0; i < pointCount; i++)
            {
                if (matchDistance.at<float>(i, 0) >= 0.6 * matchDistance.at<float>(i, 1)) continue;

                DMatch dmatches(i, macthIndex.at<int>(i, 0), matchDistance.at<float>(i, 1));
                GoodMatchePoints.push_back(dmatches);
            }

            qDebug() << "p1 size " << keyPoint1.size();
            qDebug() << "p2 size " << keyPoint2.size();

            // Mat firstMatch;
            // drawMatches(imageOrigin2, keyPoint2, imageOrigin1, keyPoint1, GoodMatchePoints, firstMatch);
            // imshow("first_match ", firstMatch);
            // waitKey();
            // imwrite("first_match_orb.jpg", first_match);

            qDebug() << "C " << QDateTime::currentDateTime().toMSecsSinceEpoch();

            // 计算两张图的相对位置
            qDebug() << "pointCount " << GoodMatchePoints.size();


            double diffY = 0.0;
            double diffX = 0.0;

            int count = 0;
            for (auto &matchPoint : GoodMatchePoints)
            {
                if (matchPoint.trainIdx < 0 || matchPoint.queryIdx < 0) continue;
                diffY += (keyPoint1.at(matchPoint.trainIdx).pt.y - keyPoint2.at(matchPoint.queryIdx).pt.y);
                diffX += (keyPoint1.at(matchPoint.trainIdx).pt.x - keyPoint2.at(matchPoint.queryIdx).pt.x);

                count++;
            }

            if (0 == count)
            {
                diffX = 0;
                diffY = 0;
            }
            else
            {

            }

            diffX = (count == 0) ? 0 : (diffX / count);
            diffY = (count == 0) ? 0 : (diffY / count);

            SourceItemInfo info {index, path, 0, 0, 0, 0, 0, count > 0};
            PictureRectItem *item = new PictureRectItem(path, index, count > 0);
            info.width = item->width();
            info.height = item->height();
            info.rotate = item->rotate();
            info.centerx = preInfo.centerx + diffX;
            info.centery = preInfo.centery + diffY;

            mMapSourceInfo.insert(index, info);

            emit AppSignal::getInstance()->sgl_add_new_graphics_item(index, item);

            qDebug() << "diff " << diffX << " " << diffY;
        }
    };

    std::thread th(func);
    th.detach();

//    mTotalPictureNumber++;
//    qDebug() << "mTotalPictureNumber " << mTotalPictureNumber;
//    PictureRectItem *item = new PictureRectItem(path, index);
//    mGraphicsScene.addItem(item);
//    item->setPos(1920 * -0.5 + (mTotalPictureNumber - 1) * 1930, 1200 * -0.5);

//    QRectF sceneRect = mGraphicsScene.itemsBoundingRect();
//    sceneRect.adjust(mGraphicsView.width() * -0.5, mGraphicsView.height() * -0.5, mGraphicsView.width() * 0.5, mGraphicsView.height() * 0.5);
//    mGraphicsScene.setSceneRect(sceneRect);
}

void WidgetGraphicsView::slot_wheel_graphics_view(float value)
{
    if (value < 0) mScaleValue = std::max((int32_t)0, mScaleValue - 2);
    else mScaleValue = std::min((int32_t)240, mScaleValue + 2);

    qreal scale = qPow(qreal(2), (mScaleValue - 120) / qreal(10));

    //qDebug() << "scale " << scale;

    QTransform matrix;
    matrix.scale(scale, scale);
    mGraphicsView.setTransform(matrix);
}

void WidgetGraphicsView::slot_save_current_visible_scene()
{
    // 此处截图功能根据每张图片的位置信息，使用 opencv 拼接图片
}

void WidgetGraphicsView::slot_add_new_graphics_item(int index, PictureRectItem *item)
{
    qDebug() << "WidgetGraphicsView::slot_add_new_graphics_item";
    mGraphicsScene.addItem(item);

    // 根据信息，获取 item 所在位置
    SourceItemInfo info = mMapSourceInfo.value(index);

    qDebug() << "Pos " << index << info.centerx  << info.centery;

    item->setPos(info.centerx - info.width * 0.5, info.centery - info.height * 0.5);
    // 根据序号，固定它们的 Z 序，不依赖与添加顺序
    item->setZValue(index);

    QRectF sceneRect = mGraphicsScene.itemsBoundingRect();
    sceneRect.adjust(mGraphicsView.width() * -100, mGraphicsView.height() * -100, mGraphicsView.width() * 100, mGraphicsView.height() * 100);
    mGraphicsScene.setSceneRect(sceneRect);

    // 定位新增的图元
    mGraphicsView.centerOn(info.centerx, info.centery);

    emit AppSignal::getInstance()->sgl_add_new_graphics_item_status(index, item->matched());
}

void WidgetGraphicsView::slot_btn_reset_view_click()
{
    // 重置视图为所有图元区域
    mGraphicsView.centerOn(mGraphicsScene.itemsBoundingRect().center());
    mGraphicsView.fitInView(mGraphicsScene.itemsBoundingRect(), Qt::KeepAspectRatio);

    // 重置缩放系数
    mScaleValue = (qLn(mGraphicsView.transform().m11()) / qLn(2)) * 10 + 120;
}

void WidgetGraphicsView::slot_btn_close_select_click()
{
    emit AppSignal::getInstance()->sgl_close_slected_graphics_item();
}

void WidgetGraphicsView::slot_btn_restore_view_click()
{
    auto lsitItem = mGraphicsScene.items();
    for (auto &item : lsitItem)
    {
        PictureRectItem *realItem = dynamic_cast<PictureRectItem*>(item);
        if (nullptr == realItem) continue;

        // 恢复定位
        int index = realItem->index();
        if (!mMapSourceInfo.contains(index)) continue;
        SourceItemInfo info = mMapSourceInfo.value(index);
        item->setPos(info.centerx - info.width * 0.5, info.centery - info.height * 0.5);
    }
}

void WidgetGraphicsView::slot_btn_save_view_click()
{
    qDebug() << "WidgetGraphicsView::slot_btn_save_view_click";
    std::lock_guard<std::mutex> lock(mMapMutex);
    if (mMapSourceInfo.empty())
    {
        // 给个消息
        emit AppSignal::getInstance()->sgl_system_output_message(3, "视图为空，无法保存");
        return;
    }

    // 不想处理同步问题， 这里直接拷贝 Map
    auto tempMap = mMapSourceInfo;



    // 线程中开始处理图像拼接
    auto func = [tempMap]
    {
        if (tempMap.size() >= 2)
        {
            emit AppSignal::getInstance()->sgl_system_output_message(3, "开始保存结果图片，请等待 ···");
        }

        // 此处计算结果区域大小，不能依靠 itemsBoundingRect ，因为 item 移动后这个值会被改变
        // 如果改小了，由于是按照拼接的位置来拷贝的，区域会溢出导致程序崩溃

        int minX = 0x0fffffff;
        int minY = 0x0fffffff;
        int maxX = -0x0fffffff;
        int maxY = -0x0fffffff;

        for (auto &info : tempMap)
        {
            minX = qMin(minX, int(info.centerx - info.width * 0.5));
            minY = qMin(minY, int(info.centery - info.height * 0.5));

            maxX = qMax(maxX, int(info.centerx + info.width * 0.5));
            maxY = qMax(maxY, int(info.centery + info.height * 0.5));
        }

        QRectF rect = QRectF(minX, minY, maxX - minX, maxY - minY);

        //qDebug() << "itemsBoundingRect " << rect;

        // 创建空的结果图像
        Mat dst(rect.height(), rect.width(), CV_8UC3); dst.setTo(0);
        for (auto &info : tempMap)
        {
            Mat image = imread(info.path.toStdString());
            if (nullptr == image.data) continue;
            int x = info.centerx - info.width * 0.5 - rect.x();
            int y = info.centery - info.height * 0.5 - rect.y();
            image.copyTo(dst(Rect(x, y, image.cols, image.rows)));
        }

        // 默认存到桌面
        QString desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
        QString name = QString("%1/dst_save_%2.png").arg(desktop, QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"));

        bool status = imwrite(name.toStdString(), dst, std::vector<int>{IMWRITE_PNG_COMPRESSION, 36});
        emit AppSignal::getInstance()->sgl_system_output_message(status ? 1 : 2, "图像保存成功（在桌面上）");
    };

    std::thread th(func);
    th.detach();
}

void WidgetGraphicsView::slot_del_all_graphics_item()
{
    std::lock_guard<std::mutex> lock(mMapMutex);
    mGraphicsScene.clear();
    mMapSourceInfo.clear();
}

void WidgetGraphicsView::slot_close_slected_graphics_item()
{
    auto list = mGraphicsScene.selectedItems();
    for (auto &item : list)
    {
        mGraphicsScene.removeItem(item);

        PictureRectItem *realItem = dynamic_cast<PictureRectItem*>(item);
        if (nullptr == realItem) continue;
        qDebug() << "item close " << realItem->index() << realItem->path();

        // 清理缓存的键值对
        std::lock_guard<std::mutex> lock(mMapMutex);
        mMapSourceInfo.remove(realItem->index());

        // 删除的图形项  还原到 列表
        emit AppSignal::getInstance()->sgl_revert_graphics_item_to_list(realItem->path(), realItem->index());

        // removeItem 是不清理内存的，要手动清理（已测试）
        delete item;
    }
}
